﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Remoting.Messaging;
using System.Text;

namespace OnlineMealOrdering.Domain.Base
{
    /// <summary>
    /// <see cref="IDomainContext"/> defines an interface to mark an entity added to or removed from the context,
    /// or retrieve specified type entity set.
    /// 
    /// Especially attention:
    /// This interface should be derived when using in the concreate business domain.
    /// </summary>
    public interface IDomainContext
    {
        /// <summary>
        /// Check if the context has been relased.
        /// </summary>
        bool IsDisposed { get; set; }

        /// <summary>
        /// Explicitly mark the entity has been added in the context
        /// </summary>
        /// <typeparam name="TEntity">entity type</typeparam>
        /// <param name="entity">entity instance</param>
        TEntity Add<TEntity>(TEntity entity) where TEntity : class;

        /// <summary>
        /// Explicitly mark the entity has been removed form the context
        /// </summary>
        /// <typeparam name="TEntity">entity type</typeparam>
        /// <param name="entity">entity instance</param>
        void Remove<TEntity>(TEntity entity) where TEntity : class;

        /// <summary>
        /// Get entity set
        /// </summary>
        /// <typeparam name="TEntity">entity type</typeparam>
        /// <returns>Entity set supporting LinQ</returns>
        IQueryable<TEntity> EntitySet<TEntity>() where TEntity : class;

        /// <summary>
        ///  Get entity set
        /// </summary>
        /// <typeparam name="TEntity">entity type</typeparam>
        /// <param name="predicate">predicate expression</param>
        /// <returns>Entity set supporting LinQ</returns>
        IQueryable<TEntity> EntitySet<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class;

        /// <summary>
        /// Find entity by key values
        /// </summary>
        /// <typeparam name="TEntity">entity type</typeparam>
        /// <param name="keyValues">key values</param>
        /// <returns>if exists return the entity instance, otherwise return null</returns>
        TEntity Find<TEntity>(params object[] keyValues) where TEntity : class;

        /// <summary>
        /// Submit entity changes
        /// </summary>
        void SubmitChanges();
    }




    /// <summary>
    /// Domain context with which the domain model can access entities
    /// </summary>
    public abstract class DomainContext : IDomainContext
    {
        public bool IsDisposed { get; set; }

        public abstract TEntity Add<TEntity>(TEntity entity) where TEntity : class;

        public abstract void Remove<TEntity>(TEntity entity) where TEntity : class;

        public abstract IQueryable<TEntity> EntitySet<TEntity>() where TEntity : class;

        public abstract IQueryable<TEntity> EntitySet<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate) where TEntity : class;

        public abstract TEntity Find<TEntity>(params object[] keyValues) where TEntity : class;

        public abstract void SubmitChanges();

        #region static methods

        /// <summary>
        /// Get the default domain context, once the domain contexts are more than one, please use GetContext method.
        /// If the current domain context does not exist, throw exception.
        /// </summary>
        public static IDomainContext Current
        {
            get
            {
                return DomainManager.GetDefaultContext();
            }
        }

        /// <summary>
        /// Get the domain context with specified domain context interface type. 
        /// </summary>
        /// <typeparam name="DomainContextInterface">domain context interface</typeparam>
        /// <returns>if exists return domain context instance, otherwise return null</returns>
        public static DomainContextInterface GetContext<DomainContextInterface>()
            where DomainContextInterface : IDomainContext
        {
            return DomainManager.GetContext<DomainContextInterface>();
        }

        #endregion
    }

    /// <summary>
    /// <see cref="DomainManager"/> is responsible for manage domain contexts and services that will be consumed by domain model. 
    /// </summary>
    public class DomainManager
    {
        static Dictionary<Type, object> _globalServices = new Dictionary<Type, object>();

        /// <summary>
        /// Domain contexts live in the call context
        /// </summary>
        static Dictionary<Type, object> DomainContexts
        {
            get
            {
                var domainContexts = CallContext.GetData("__DomainContexts") as Dictionary<Type, object>;

                if (domainContexts == null)
                {
                    domainContexts = new Dictionary<Type, object>();
                    CallContext.SetData("__DomainContexts", domainContexts);
                }

                return domainContexts;
            }
        }

        /// <summary>
        /// Register domain context
        /// </summary>
        /// <typeparam name="DomainContextInterface">domain context interface</typeparam>
        /// <param name="domainContext">domain context instance</param>
        public static void RegisterContext<DomainContextInterface>(DomainContextInterface domainContext)
            where DomainContextInterface : IDomainContext
        {
            if (DomainContexts.ContainsKey(typeof(DomainContextInterface))
                && false == ((DomainContextInterface)DomainContexts[typeof(DomainContextInterface)]).IsDisposed)
                throw new ApplicationException(string.Format("DomainContext conflict. There has existed an instance of {0} whose 'IsDiposed' is false. Please make sure close current domain context before open another.", typeof(DomainContextInterface).FullName));

            DomainContexts[typeof(DomainContextInterface)] = domainContext;
        }

        /// <summary>
        /// Unregister domain context
        /// </summary>
        /// <typeparam name="DomainContextInterface">domain context interface</typeparam>
        public static void UnregisterContext<DomainContextInterface>()
            where DomainContextInterface : IDomainContext
        {
            if (DomainContexts.ContainsKey(typeof(DomainContextInterface)))
                DomainContexts.Remove(typeof(DomainContextInterface));
        }

        /// <summary>
        /// Get domain context
        /// </summary>
        /// <typeparam name="DomainContextInterface">domain context interface</typeparam>
        /// <returns>domain context instance</returns>
        public static DomainContextInterface GetContext<DomainContextInterface>()
            where DomainContextInterface : IDomainContext
        {
            return (DomainContextInterface)DomainContexts[typeof(DomainContextInterface)];
        }

        /// <summary>
        /// Get default domain context
        /// </summary>
        /// <returns>default domain context instance</returns>
        public static IDomainContext GetDefaultContext()
        {
            if (DomainContexts.Count >= 0)
                return (IDomainContext)DomainContexts.FirstOrDefault().Value;
            else
                throw new ApplicationException("Cannot find any registered DomainContext!");
        }

        /// <summary>
        /// Register service implementation with the service interface in the current call context
        /// </summary>
        /// <typeparam name="IService">service interface</typeparam>
        /// <param name="service">service instance</param>
        public static void RegisterContextService<IService>(IService service)
        {
            CallContext.SetData("__DomainContext_" + typeof(IService).FullName, service);
        }

        /// <summary>
        /// Get service instance with the service interface in the current call context
        /// </summary>
        /// <typeparam name="IService">service interface</typeparam>
        /// <returns>if exists return service instance,otherwise return null</returns>
        public static IService GetContextService<IService>()
        {
            return (IService)CallContext.GetData("__DomainContext_" + typeof(IService).FullName);
        }

        /// <summary>
        /// Register service implementation with the service interface 
        /// </summary>
        /// <typeparam name="IService">service interface</typeparam>
        /// <param name="service">service instance</param>
        public static void RegisterGlobalService<IService>(IService service)
        {
            _globalServices[typeof(IService)] = service;
        }

        /// <summary>
        /// Get service instance with the service interface
        /// </summary>
        /// <typeparam name="IService">service interface</typeparam>
        /// <returns>if exists return service instance,otherwise return null</returns>
        public static IService GetGlobalService<IService>()
        {
            return (IService)_globalServices[typeof(IService)];
        }
    }
}
